package com.visualexpress.shards;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.*;
import org.hibernate.shards.ShardId;
import org.hibernate.shards.ShardedConfiguration;
import org.hibernate.shards.cfg.*;
import org.hibernate.shards.loadbalance.RoundRobinShardLoadBalancer;
import org.hibernate.shards.strategy.*;
import org.hibernate.shards.strategy.access.SequentialShardAccessStrategy;
import org.hibernate.shards.strategy.access.ShardAccessStrategy;
import org.hibernate.shards.strategy.resolution.AllShardsShardResolutionStrategy;
import org.hibernate.shards.strategy.resolution.ShardResolutionStrategy;
import org.hibernate.shards.strategy.selection.RoundRobinShardSelectionStrategy;
import org.hibernate.shards.strategy.selection.ShardSelectionStrategy;

import com.visualexpress.domain.Shipment;
import com.visualexpress.domain.Site;

/**
 * Configures and provides access to Hibernate sessions, tied to the current
 * thread of execution. Follows the Thread Local Session pattern, see
 * {@link http://hibernate.org/42.html }.
 */
public class HibernateSessionFactory {

	/**
	 * Location of hibernate.cfg.xml file. Location should be on the classpath
	 * as Hibernate uses #resourceAsStream style lookup for its configuration
	 * file. The default classpath location of the hibernate config file is in
	 * the default package. Use #setConfigFile() to update the location of the
	 * configuration file for the current session.
	 */
	private static String CONFIG_FILE_0 = "shard0.hibernate.cfg.xml";
	private static String CONFIG_FILE_1 = "shard1.hibernate.cfg.xml";
	private static String CONFIG_FILE_2 = "shard2.hibernate.cfg.xml";
	private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
	private static Configuration prototypeConfig;
	private static List<ShardConfiguration> shardConfigs;
	private static Hashtable<ShardStrategyFactory, SessionFactory> sessionFactorys = new Hashtable();

	static {
//		 System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY,"org.apache.naming.java.javaURLContextFactory");
		buildConfigration();
	}

	private static void buildConfigration() {
		try {
			Configuration configuration = new Configuration();
			prototypeConfig = configuration.configure(CONFIG_FILE_0);
			shardConfigs = new ArrayList<ShardConfiguration>();
			shardConfigs.add(buildShardConfig("shard0.hibernate.cfg.xml"));
			shardConfigs.add(buildShardConfig("shard1.hibernate.cfg.xml"));
			shardConfigs.add(buildShardConfig("shard2.hibernate.cfg.xml"));

			ShardConfigurationUtil.setScList(shardConfigs);

		} catch (Exception e) {
			System.err.println("%%%% Error Creating SessionFactory %%%%");
			e.printStackTrace();
		}
	}

	private static ShardConfiguration buildShardConfig(String configFile) {
		Configuration config = new Configuration().configure(configFile);
		return new ConfigurationToShardConfigurationAdapter(config);
	}

	/**
	 * Returns the ThreadLocal Session instance. Lazy initialize the
	 * <code>SessionFactory</code> if needed.
	 * 
	 * @return Session
	 * @throws HibernateException
	 */
	public static Session getSession(ShardStrategyFactory ssf) {
		System.out.println("Factory size:"+sessionFactorys.size());
		SessionFactory sessionFac = getSessionFactory(ssf);
		if (sessionFac != null)
			return sessionFac.openSession();
		else
			return null;
	}

//	public static Session getSession() {
//		ShardStrategyFactory ssf = new VEShardStrategyFactory();
//		return getSession(ssf);
//	}

//	public static Session getSession(ShardStrategyFactory ssf) {
//		ShardStrategyFactory ssf = getStrategyFactory(clazz);
//		return getSession(ssf);
//	}

	private static List<SessionFactory> sfList;

	public static void SaveToAllSites(Object obj) {
		if (sfList == null) {
			sfList = new ArrayList<SessionFactory>();
			for (ShardConfiguration sc : ShardConfigurationUtil
					.getAllShardConfigurations()) {
				ShardId id = new ShardId(sc.getShardId());
				ShardStrategyFactory shardStrategyFactory = new SpecifiedSiteShardStrategyFactory(
						id);
				ShardedConfiguration shardedConfig = new ShardedConfiguration(
						prototypeConfig, shardConfigs, shardStrategyFactory);
				SessionFactory sessionFactory = shardedConfig
						.buildShardedSessionFactory();
				sfList.add(sessionFactory);
			}
		}
		for (SessionFactory sf : sfList) {
			Session session = sf.openSession();

			Transaction tx = session.beginTransaction();
			session.save(obj);
			tx.commit();
		}
	}

	private static SessionFactory getSessionFactory(
			ShardStrategyFactory shardStrategyFactory) {
		SessionFactory sf = sessionFactorys.get(shardStrategyFactory);
		if (sf == null) {
			ShardedConfiguration shardedConfig = new ShardedConfiguration(
					prototypeConfig, shardConfigs, shardStrategyFactory);
			SessionFactory sessionFactory = shardedConfig
					.buildShardedSessionFactory();
			sessionFactorys.put(shardStrategyFactory, sessionFactory);
			return sessionFactory;
		} else
			return sf;
	}

//	private static ShardStrategyFactory getStrategyFactory(Class clazz) {
//		ShardStrategyFactory ssf = strategyFactorys.get(clazz);
//		if (ssf == null) {
//			if (clazz.getSimpleName().equals(Shipment.class.getSimpleName())) {
//				ssf = new ShipmentShardStrategyFactory();
//			} else if (clazz.getSimpleName().equals(Site.class.getSimpleName())) {
//				ssf = new SiteShardStrategyFactory();
//			}
//			strategyFactorys.put(clazz, ssf);
//
//		}
//		return ssf;
//	}

	// /**
	// * Close the single hibernate session instance.
	// *
	// * @throws HibernateException
	// */
	// public static void closeSession() throws HibernateException {
	// Session session = (Session) threadLocal.get();
	// threadLocal.set(null);
	//
	// if (session != null) {
	// session.close();
	// }
	// }

}